﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Threading;

namespace PhoneApp.Utilities
{
    public enum EnumLogLevel
    {
        NONE = 0,
        TRACE,
        DEBUG,
        INFO,
        WARN,
        ERROR,
        FATAL
    }

    public class LogEntry
    {
        public LogEntry(string msg)
        {
            _msg = msg;
        }

        private string _msg;

        public DateTime TimeStamp
        {
            get;
            set;
        }

        public EnumLogLevel LogLevel
        {
            get;
            set;
        }

        public string ThreadId
        {
            get;
            set;
        }

        public string FunctionName
        {
            get;
            set;
        }

        public override string ToString()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();

            sb.Append(TimeStamp.ToString("[yyyy-MM-dd HH:mm:ss.ffff]", CultureInfo.InvariantCulture));
            sb.AppendFormat("[{0}]", LogLevel.ToString());
            sb.AppendFormat("[{0}]", ThreadId);
            sb.AppendFormat("[{0}]", FunctionName);
            sb.Append(_msg);

            return sb.ToString();
        }
    }

    /// <summary>
    /// This logger uses two queues to separate the caller thread operation and the actual
    /// storage writing operation for better response.
    /// </summary>
    public class MyLogger
    {
        private static object _syncRoot = new Object();
        private static MyLogger _instance = null;
        private static System.Collections.Generic.Queue<LogEntry> _logQueue;
        private static System.Collections.Generic.Queue<LogEntry> _wlogQueue;
        private System.Windows.Threading.DispatcherTimer _dTimer;

        private Thread _bgThread;
        private AutoResetEvent _evQueTriggered = new AutoResetEvent(false);

        public bool IncludePrefix { get; set; }

        public bool IncludeAuxInfo { get; set; }

        public EnumLogLevel LogLevel { get; set; }

        public long SwitchLogSize { get; set; }

        public string LogFileName { get; set; }

        public int QueBufferSize { get; set; }

        public bool IsBackground { get; set; }

        public int AutoFlushInterval
        {
            get
            { return _dTimer.Interval.Seconds; }

            set
            {
                if (_dTimer.IsEnabled)
                {
                    _dTimer.Stop();
                    Trace("dispatcherTimer stopped");
                }

                if (value <= 0)
                    return;

                _dTimer.Interval = TimeSpan.FromSeconds(value);
                _dTimer.Start();
                Trace("dispatcherTimer started");
            }
        }

        public static MyLogger Instance
        {
            get
            {
                lock (_syncRoot)
                {
                    if (_instance == null)
                    {
                        _instance = new MyLogger();
                    }
                    return _instance;
                }
            }
        }

        private MyLogger()
        {
            LogFileName = "app.log";
            LogLevel = EnumLogLevel.DEBUG;
            IncludePrefix = true;
            IncludeAuxInfo = false;
            SwitchLogSize = 1024 * 1024;
            QueBufferSize = 100;
            IsBackground = false;

            _logQueue = new System.Collections.Generic.Queue<LogEntry>();
            _wlogQueue = new System.Collections.Generic.Queue<LogEntry>();

            _dTimer = new System.Windows.Threading.DispatcherTimer();
            _dTimer.Tick += new EventHandler(dispatcherTimer_Tick);

            AutoFlushInterval = 5;  //seconds

            Trace("MyLogger instantiated");
        }

        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            lock (_wlogQueue)
            {
                flushQue();
                Trace("Dispatcher Timer auto flushed");
            }
        }

        /// <summary>
        /// Create a background thread to perform the writing operation,
        /// which will never block the caller thread and enhanced the performance greatly.
        /// However, there's an issue to be noticed:
        /// when App submits a lot of log entries in a very short time and then exit
        /// immediately, the background thread may have not enough time to write all
        /// of them into the storage, which will cause data loss!
        /// </summary>
        public void Background()
        {
            IsBackground = true;

            _bgThread = new Thread(checkAndFlushQue);
            _bgThread.Start();
        }

        private void checkAndFlushQue()
        {
            _evQueTriggered.WaitOne();
            flushQue();
        }

        [Conditional("DEBUG")]
        public void Trace(string message, params object[] args)
        {
            writeLog(EnumLogLevel.TRACE, message, args);
        }

        [Conditional("DEBUG")]
        public void Trace(string message)
        {
            writeLog(EnumLogLevel.TRACE, message, null);
        }

        [Conditional("DEBUG")]
        public void Debug(string message, params object[] args)
        {
            writeLog(EnumLogLevel.DEBUG, message, args);
        }

        [Conditional("DEBUG")]
        public void Debug(string message)
        {
            writeLog(EnumLogLevel.DEBUG, message, null);
        }

        [Conditional("DEBUG")]
        public void Info(string message, params object[] args)
        {
            writeLog(EnumLogLevel.INFO, message, args);
        }

        [Conditional("DEBUG")]
        public void Info(string message)
        {
            writeLog(EnumLogLevel.INFO, message, null);
        }

        [Conditional("DEBUG")]
        public void Warn(string message, params object[] args)
        {
            writeLog(EnumLogLevel.WARN, message, args);
        }

        [Conditional("DEBUG")]
        public void Warn(string message)
        {
            writeLog(EnumLogLevel.WARN, message, null);
        }

        [Conditional("DEBUG")]
        public void Error(string message, params object[] args)
        {
            writeLog(EnumLogLevel.ERROR, message, args);
        }

        [Conditional("DEBUG")]
        public void Error(string message)
        {
            writeLog(EnumLogLevel.ERROR, message, null);
        }

        [Conditional("DEBUG")]
        public void Fatal(string message, params object[] args)
        {
            writeLog(EnumLogLevel.FATAL, message, args);
        }

        [Conditional("DEBUG")]
        public void Fatal(string message)
        {
            writeLog(EnumLogLevel.FATAL, message, null);
        }

        [Conditional("DEBUG")]
        public void FunctionEntry()
        {
            try
            {
                string message = null;
                lock (_syncRoot)
                {
                    StackFrame frame = new StackTrace().GetFrame(1);
                    string function = frame.GetMethod().DeclaringType.FullName + "." + frame.GetMethod().ToString();
                    message = string.Format("Function({0}) Entry", function);
                }
                writeLog(EnumLogLevel.TRACE, message, null);
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.Message);
            }
        }

        [Conditional("DEBUG")]
        public void FunctionExit()
        {
            StackFrame frame = new StackTrace().GetFrame(1);
            string function = frame.GetMethod().DeclaringType.FullName + "." + frame.GetMethod().ToString();
            string message = string.Format("Function({0}) Exit", function);
            writeLog(EnumLogLevel.TRACE, message, null);
        }

        public string Content()
        {
            lock (_syncRoot)
            {
                string content = null;
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                //using (var store = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null))
                {
                    if (store.FileExists(LogFileName))
                    {
                        Stream stream = store.OpenFile(LogFileName, FileMode.Open, FileAccess.Read, FileShare.Read);
                        stream.Seek(0, SeekOrigin.Begin);
                        using (StreamReader sr = new StreamReader(stream))
                        {
                            content = sr.ReadToEnd();
                        }
                    }
                }

                return content;
            }
        }

        private void writeLog(EnumLogLevel level, string message, object[] args, bool condition = true)
        {
            if (level < LogLevel)
            {
                return;
            }

            string formattedMessage = message;
            if (args != null)
            {
                formattedMessage = string.Format(CultureInfo.InvariantCulture, message, args);
            }

            LogEntry entry = new LogEntry(formattedMessage);
            if (IncludePrefix)
            {
                entry.TimeStamp = DateTime.Now;
                entry.ThreadId = Thread.CurrentThread.ManagedThreadId.ToString();
                entry.LogLevel = level;

                if (IncludeAuxInfo)
                {
                    lock (_syncRoot)
                    {
                        StackFrame frame = new StackTrace().GetFrame(2);
                        //string function = frame.GetMethod().DeclaringType.FullName + " " + frame.GetMethod().ToString();
                        string function = frame.GetMethod().ToString();
                        entry.FunctionName = function;
                    }
                }
            }

            lock (_logQueue)
            {
                _logQueue.Enqueue(entry);

                if (_logQueue.Count > QueBufferSize)
                {
                    flushQueWrapper();
                }
            }
        }

        private void flushQueWrapper()
        {
            lock (_syncRoot)
            {
                while (_logQueue.Count > 0)
                {
                    _wlogQueue.Enqueue(_logQueue.Dequeue());
                }
            }

            // FIXME::
            // Restartable entrance?
            // When queue is full, caller thread will signal writer thread.
            // But, if writer thread has not finished the IO operation and caller
            // send more data into the queue, it will signal the writer thread
            // again and again.
            if (IsBackground)
                _evQueTriggered.Set();
            else
                flushQue();
        }

        private void flushQue()
        {
            //Console.WriteLine(DateTime.Now.ToString() + " flush queue");
            lock (_wlogQueue)
            {
                try
                {
                    // Does not work on Windows 7!
                    //var store = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);
                    var store = IsolatedStorageFile.GetUserStoreForApplication();
                    IsolatedStorageFileStream stream = store.OpenFile(LogFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
                    StreamWriter writer = new StreamWriter(stream);
                    writer.AutoFlush = true;

                    while (_wlogQueue.Count > 0)
                    {
                        stream.Seek(0, SeekOrigin.End);
                        string entry = _wlogQueue.Dequeue().ToString();
                        writer.WriteLine(entry);

                        if (stream.Length + entry.Length > SwitchLogSize)
                            SwitchLog(ref stream, ref writer);
                    }

                    writer.Close();
                }
                catch (System.Exception e)
                {
                    Console.WriteLine("Exception occurred!" + e.GetType());
                }
                finally
                {
                }
            }
        }

        private void SwitchLog(ref IsolatedStorageFileStream stream, ref StreamWriter writer)
        {
            lock (_syncRoot)
            {
                try
                {
                    IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();
                    //IsolatedStorageFile store = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);

                    writer.Close();
                    stream.Close();

                    string bakFile = LogFileName + "." + DateTime.Now.ToString("yyyyMMdd");
                    if (store.FileExists(bakFile))
                        store.DeleteFile(bakFile);

                    store.MoveFile(LogFileName, bakFile);

                    stream = store.OpenFile(LogFileName, FileMode.OpenOrCreate,
                                            FileAccess.ReadWrite, FileShare.Read);
                    writer = new StreamWriter(stream);
                }
                catch (System.Exception e)
                {
                    Console.WriteLine("Exception occurred!" + e.GetType());
                }
                finally
                {
                }
            }
        }
    }
}